import { createIdentifier, Inject } from '@univerjs/core';
import { Disposable, type IFreeze, IRange, IUniverInstanceService, type Workbook } from '@univerjs/core';
import { SheetPrintInterceptorService, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
import { SheetsSelectionsService, WorkbookSelections } from '@univerjs/sheets';
import { IRenderManagerService, SpreadsheetSkeleton } from '@univerjs/engine-render';
import { UniverType } from '@univerjs/protocol';
import {
  JnpfPaperAlign,
  JnpfPaperMargin,
  JnpfPaperMarginType,
  JnpfPaperRotation,
  JnpfPaperScale,
  JnpfPaperSize,
  JnpfPaperType,
  JnpfPrintArea,
  JnpfPrintFreeze,
} from '../utils/define';

export interface IJnpfPrintService {
  getPaperSize: () => any;
  getPaperMargin: () => any;
  getSetting: () => { infos: any; config: any; layout: any };
  reLayout: (config: any) => void;
  reset: () => void;
  updateLayoutConfig: (config: any) => void;
  updateRenderConfig: (config: any) => void;
}

export const IJnpfPrintService = createIdentifier<IJnpfPrintService>('jnpf.print.sheet.service');

interface SheetPrintRange {
  unitId: string;
  subUnitId: string;
  range: { startRow: number; endRow: number; startColumn: number; endColumn: number };
  freeze: { startRow: number; startColumn: number; xSplit: number; ySplit: number };
  contentSize: {};
  scale: number;
}

type SheetPrintRanges = Array<SheetPrintRange>;

export class JnpfSheetsPrintService extends Disposable implements IJnpfPrintService {
  /**
   * 打印纸张配置项
   * @private
   */
  private _printConfig = {
    area: JnpfPrintArea.CurrentSheet,
    subUnitIds: [],
    paperSize: JnpfPaperType.A4,
    direction: JnpfPaperRotation.Portrait,
    scale: JnpfPaperScale.Origin,
    customScale: 1,
    freeze: [JnpfPrintFreeze.Row, JnpfPrintFreeze.Column],
    margin: JnpfPaperMarginType.Normal,
    WorkbookTitleText: '',
  };
  private _printPages = [];
  /**
   * 打印内容配置项
   * @private
   */
  private _contentConfig = {
    gridlines: false,
    hAlign: JnpfPaperAlign.Middle,
    vAlign: JnpfPaperAlign.Start,
    headerFooter: [],
  };
  private _workbookSelections: WorkbookSelections;

  constructor(
    @IUniverInstanceService protected readonly _univerInstanceService: IUniverInstanceService,
    @IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
    @Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
    @Inject(SheetPrintInterceptorService) private readonly _sheetPrintInterceptorService: SheetPrintInterceptorService,
  ) {
    super();
    this.getWorkSelections();
  }

  getWorkSelections() {
    const workbook = this.getWorkbook();
    if (!workbook) return;
    this._workbookSelections = this._selectionManagerService.getWorkbookSelections(workbook.getUnitId());
  }

  get layoutInfos() {
    return this._printPages;
  }

  get contentConfig() {
    return this._contentConfig;
  }

  get printConfig() {
    return this._printConfig;
  }

  get paperSize() {
    const { paperSize, direction } = this._printConfig ?? {};
    const paper = JnpfPaperSize[paperSize];
    return direction === JnpfPaperRotation.Portrait ? { ...paper } : { w: paper?.h, h: paper?.w };
  }

  get paperMargin() {
    return JnpfPaperMargin[this._printConfig.margin];
  }

  getPaperSize() {
    return this.paperSize;
  }

  getPaperMargin() {
    return this.paperMargin;
  }

  getSetting() {
    return { infos: this._printPages, config: this._contentConfig, layout: this._printConfig };
  }

  updateLayoutConfig(config) {
    this._printConfig = { ...this._printConfig, ...config };
    // this.reLayout();
  }

  updateRenderConfig(config) {
    this._contentConfig = { ...this._contentConfig, ...config };
  }

  reLayout() {
    this._printPages = this._computedPages();
  }

  reset() {
    this._printPages = [];
    this._printConfig = { ...this._printConfig, area: JnpfPrintArea.CurrentSheet };
  }

  getSkeleton(unitId, subUnitId) {
    return this._renderManagerService.getRenderById(unitId)?.with(SheetSkeletonManagerService).getOrCreateSkeleton({
      sheetId: subUnitId,
    });
  }

  getSkeletonByUnit(unitId, subUnitId) {
    const unit = this._renderManagerService.getRenderById(unitId)?.with(SheetSkeletonManagerService).getUnitSkeleton(unitId, subUnitId);
    if (unit) {
      return unit.skeleton;
    }
  }

  getWorkbook() {
    return this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverType.UNIVER_SHEET);
  }

  getWorksheet(sheetId) {
    const workbook = this.getWorkbook();
    if (workbook) return workbook.getSheetBySheetId(sheetId);
    return null;
  }

  /**
   * 计算打印的分页数据
   */
  _computedPages() {
    const subSheetRange = this._computedPrintRanges();
    const pages = [];
    subSheetRange.forEach(range => {
      if (range) {
        const page = this._computedPrintPage(range);
        if (page) {
          pages.push(page);
        }
      }
    });
    return pages;
  }

  /**
   * 根据打印范围获取打印内容
   * 1.currentSheet：打印当前区域
   * 2.currentSelection: 打印选择区域
   * 3.默认打印整个工作簿
   */
  _computedPrintRanges() {
    const { area } = this._printConfig;
    const workbook = this.getWorkbook();
    switch (area) {
      case JnpfPrintArea.CurrentSheet: {
        const sheetId = workbook.getActiveSheet().getSheetId();
        if (sheetId != null) {
          const finalRanges = this._computedSheetRanges(sheetId);
          return finalRanges ? [finalRanges] : [this._computedSelectionRanges()];
        }
        return [];
      }
      case JnpfPrintArea.CurrentSelection:
        return [this._computedSelectionRanges()];
      default:
        // return (subUnitIds.length ? subUnitIds : workbook.getSheets().map(sheet => sheet.getSheetId()))
        //   .map(sheet => this._computedSheetRanges(sheet))
        //   .filter(Boolean);
        return [];
    }
  }

  /**
   * 根据打印内容获取打印的分页信息
   * @param sheetPrintRanges 打印内容
   */
  _computedPrintPage(sheetPrintRanges: SheetPrintRanges) {
    const { subUnitId, range, freeze, unitId, scale, contentSize } = sheetPrintRanges;
    const skeleton = this.getSkeleton(unitId, subUnitId);
    if (!skeleton) return;
    const { rowHeightAccumulation, columnWidthAccumulation } = skeleton;
    const { startRow, startColumn, endColumn, endRow } = range;
    const calcRow = start => {
      let end = start;
      const offset = start === 0 ? 0 : rowHeightAccumulation[start - 1];
      for (; end < endRow; end++) {
        const current = (rowHeightAccumulation[end] - offset) * scale;
        const next = (rowHeightAccumulation[end + 1] - offset) * scale;
        if (current > contentSize.h || (current < contentSize.h && next > contentSize.h)) return { startRow: start, endRow: end - 1 };
      }
      return { startRow: start, endRow: end };
    };
    const calcColumn = start => {
      let end = start;
      const offset = start === 0 ? 0 : columnWidthAccumulation[start - 1];
      for (; end < endColumn; end++) {
        const current = (columnWidthAccumulation[end] - offset) * scale;
        const next = (columnWidthAccumulation[end + 1] - offset) * scale;
        if ((current < contentSize.w && next > contentSize.w) || current >= contentSize.w) return { startColumn: start, endColumn: end - 1 };
      }
      return { startColumn: start, endColumn: end };
    };
    const rows = [];
    for (let start = startRow; start <= endRow;) {
      const row = calcRow(start);
      rows.push(row);
      start = row.endRow + 1;
    }
    const columns = [];
    for (let start = startColumn; start <= endColumn;) {
      const column = calcColumn(start);
      columns.push(column);
      start = column.endColumn + 1;
    }
    const pages = [];
    for (let row = 0; row < rows.length; row++) {
      for (let col = 0; col < columns.length; col++) {
        pages.push({ ...rows[row], ...columns[col] });
      }
    }
    return { unitId: unitId, subUnitId: subUnitId, pages: pages, freeze, scale: scale };
  }

  /**
   * 通过工作簿ID获取该工作簿的打印区域
   * @param sheetId
   */
  _computedSheetRanges(sheetId: string) {
    const workbook = this.getWorkbook();
    if (!workbook) return;
    const worksheet = this.getWorksheet(sheetId);
    if (!worksheet) return;
    const unitId = workbook.getUnitId();
    const subUnitId = worksheet.getSheetId();
    const skeleton = this.getSkeletonByUnit(unitId, subUnitId);
    if (!skeleton) return;
    const freeze = worksheet.getFreeze();
    const printRange = worksheet.getCellMatrixPrintRange();
    if (printRange) {
      const overflowCache = skeleton.overflowCache;
      if (overflowCache) {
        overflowCache.forValue((row, col, value) => {
          const { endColumn } = value;
          if (endColumn > printRange.endColumn) {
            printRange.endColumn = endColumn;
          }
        });
      }
      const _range = this._sheetPrintInterceptorService.interceptor.fetchThroughInterceptors(
        this._sheetPrintInterceptorService.interceptor.getInterceptPoints().PRINTING_RANGE,
      )(printRange, { unitId, subUnitId });
      return this._computedPageRange(sheetId, {
        ..._range,
        startColumn: Math.max(freeze.startColumn - freeze.xSplit, 0),
        startRow: Math.max(freeze.startRow - freeze.ySplit, 0),
      });
    }
  }

  /**
   * 获取当前选区可打印的配置信息
   */
  _computedSelectionRanges() {
    const selection = this._workbookSelections.getCurrentLastSelection();
    const workbook = this.getWorkbook();
    if (!workbook) return;
    const worksheet = workbook.getActiveSheet();
    if (!worksheet) return;
    const sheetId = worksheet.getSheetId();
    if (selection) return this._computedPageRange(sheetId, selection.range);
  }

  /**
   * 计算工作簿可打印的配置信息
   * @param sheetId 工作簿
   * @param range 打印区域
   */
  _computedPageRange(sheetId: string, range: IRange) {
    const workbook = this.getWorkbook();
    if (!workbook) return;
    const worksheet = workbook.getSheetBySheetId(sheetId);
    const config = this._printConfig;
    const unitId = workbook.getUnitId();
    const size = this.paperSize;
    const { top, right, bottom, left } = this.paperMargin;
    size.h -= top + bottom;
    size.w -= left + right;
    if (range.startRow > -1 && range.startColumn > -1) {
      const skeleton = this.getSkeleton(workbook.getUnitId(), sheetId);
      if (!worksheet || !skeleton) return null;
      let startRow = range.startRow;
      const endRow = range.endRow;
      let startColumn = range.startColumn;
      const endColumn = range.endColumn;
      const freeze = worksheet.getFreeze();
      const paperScale = this._getPaperScale(this._computedScale(skeleton, range, freeze));
      let freezeStartRow = -1,
        freezeYSplit = 0,
        freezeStartColumn = -1,
        freezeXSplit = 0;
      const columnWidthAccumulation = skeleton.columnWidthAccumulation;
      const rowHeightAccumulation = skeleton.rowHeightAccumulation;
      if (freeze.xSplit && config.freeze.includes(JnpfPrintFreeze.Column)) {
        if (endColumn >= freeze.startColumn) {
          const offsetWidth =
            paperScale * (columnWidthAccumulation[freeze.startColumn - 1] - (columnWidthAccumulation[freeze.startColumn - freeze.xSplit - 1] || 0));
          if (offsetWidth < size.w) {
            freezeStartColumn = freeze.startColumn;
            freezeXSplit = freeze.xSplit;
            size.w -= offsetWidth;
          }
        } else {
          const col1 = endColumn + 1;
          const col2 = endColumn + 1 - (freeze.startColumn - freeze.xSplit);
          const offsetWidth =
            paperScale * (columnWidthAccumulation[freezeStartColumn - 1] - (columnWidthAccumulation[freezeStartColumn - freezeXSplit] - 1 || 0));
          if (offsetWidth < size.w) {
            freezeStartColumn = col1;
            freezeXSplit = col2;
            size.w -= offsetWidth;
          }
        }
      }
      startColumn = Math.max(freezeStartColumn, startColumn);
      if (freeze.ySplit && config.freeze.includes(JnpfPrintFreeze.Row)) {
        if (endRow >= freeze.startRow) {
          const offsetHeight = rowHeightAccumulation[freeze.startRow] - rowHeightAccumulation[freeze.startRow - freeze.ySplit];
          if (offsetHeight < size.h) {
            freezeStartRow = freeze.startRow;
            freezeYSplit = freeze.ySplit;
            size.h -= offsetHeight;
          }
        } else {
          freezeStartRow = endRow + 1;
          freezeYSplit = endRow + 1 - (freeze.startRow - freeze.ySplit);
          size.h = size.h - rowHeightAccumulation[freezeStartRow] - rowHeightAccumulation[freezeStartRow - freezeYSplit];
        }
      }
      startRow = Math.max(freezeStartRow, startRow);
      return {
        unitId: unitId,
        subUnitId: sheetId,
        range: { startRow: startRow, endRow: endRow, startColumn: startColumn, endColumn: endColumn },
        freeze: { startRow: freezeStartRow, startColumn: freezeStartColumn, xSplit: freezeXSplit, ySplit: freezeYSplit },
        contentSize: size,
        scale: paperScale,
      } as SheetPrintRange;
    }
    return {
      unitId: unitId,
      subUnitId: sheetId,
      range: { startColumn: 0, endColumn: 0, startRow: 0, endRow: 0 },
      freeze: { startColumn: -1, startRow: -1, xSplit: 0, ySplit: 0 },
      contentSize: size,
      scale: 1,
    } as SheetPrintRange;
  }

  _computedScale(skeleton: SpreadsheetSkeleton, range: IRange, freeze: IFreeze) {
    const { columnWidthAccumulation, rowHeightAccumulation } = skeleton;
    const { xSplit, ySplit, startColumn: freezeStartColumn, startRow: freezeStartRow } = freeze;
    let { startRow, startColumn } = range;
    const { endColumn, endRow } = range;
    startRow = endRow < freezeStartRow ? startRow : Math.max(freezeStartRow, startRow);
    startColumn = endColumn < freezeStartColumn ? startColumn : Math.max(freezeStartColumn, startColumn);
    const { scale, customScale } = this._printConfig;
    const paperSize = this.paperSize;
    const paperMargin = this.paperMargin;
    const _xSplit = xSplit > 0 ? columnWidthAccumulation[freezeStartColumn - 1] - (columnWidthAccumulation[freezeStartColumn - xSplit - 1] || 0) : 0;
    const _ySplit = ySplit > 0 ? rowHeightAccumulation[freezeStartRow - 1] - (rowHeightAccumulation[freezeStartRow - ySplit - 1] || 0) : 0;
    const rowHeight = rowHeightAccumulation[endRow] - (rowHeightAccumulation[startRow - 1] || 0);
    const columnWidth = columnWidthAccumulation[endColumn] - (columnWidthAccumulation[startColumn - 1] || 0);
    const yScale = rowHeight + _ySplit;
    const xScale = columnWidth + _xSplit;
    switch (scale) {
      case JnpfPaperScale.Custom:
        return customScale;
      case JnpfPaperScale.FitWidth:
        return Math.min(1, (paperSize.w - paperMargin.left - paperMargin.right) / xScale);
      case JnpfPaperScale.FitHeight:
        return Math.min(1, (paperSize.h - paperMargin.top - paperMargin.bottom) / yScale);
      case JnpfPaperScale.FitPage:
        return Math.min(1, (paperSize.w - paperMargin.left - paperMargin.right) / xScale, (paperSize.h - paperMargin.top - paperMargin.bottom) / yScale);
      default:
        return 1;
    }
  }

  _getPaperScale(scale: number) {
    return Math.floor(scale * 100) / 100;
  }
}
